What is Blender Python API (bpy)?
The Blender Python API (bpy) is a powerful programming interface that allows you to automate tasks, create custom tools, and extend Blender's functionality using Python. Whether you want to create add-ons, automate repetitive tasks, or build production pipelines, bpy gives you programmatic access to almost every aspect of Blender.
Why Learn bpy?
- Automation: Automate repetitive tasks and save hours of manual work
- Customization: Create custom tools tailored to your workflow
- Pipeline Integration: Connect Blender with other software and tools
- Add-on Development: Build and share tools with the Blender community
1. Getting Started with Blender Python API
The Blender Python API is accessed through the bpy module. This module contains all the functionality you need to interact with Blender programmatically.
Accessing the Python Console
In Blender, switch to the Scripting workspace or open the Python Console (Shift+F4) to start experimenting with bpy.
Basic Structure
The bpy module is organized into several main components:
bpy.context- Access to the current context (active object, scene, etc.)bpy.data- Access to all Blender data (objects, meshes, materials, etc.)bpy.ops- Operators (actions you can perform)bpy.types- Type definitions for Blender data structures
Example: Your First Script
import bpy
# Print all objects in the scene
for obj in bpy.data.objects:
print(obj.name)
# Get the active object
active_obj = bpy.context.active_object
if active_obj:
print(f"Active object: {active_obj.name}")
print(f"Location: {active_obj.location}")
2. Customizing Blender UI with Python
You can create custom panels, buttons, and UI elements to enhance your workflow. UI customization uses the bpy.types.Panel class.
Creating a Simple Panel
import bpy
class HelloWorldPanel(bpy.types.Panel):
bl_label = "Hello World Panel"
bl_idname = "PT_HelloWorld"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Tool'
def draw(self, context):
layout = self.layout
layout.label(text="Hello, Blender!")
layout.operator("mesh.primitive_cube_add", text="Add Cube")
def register():
bpy.utils.register_class(HelloWorldPanel)
def unregister():
bpy.utils.unregister_class(HelloWorldPanel)
if __name__ == "__main__":
register()
Key UI Properties
bl_space_type- Where the panel appears (VIEW_3D, PROPERTIES, etc.)bl_region_type- Region within the space (UI, HEADER, TOOLS)bl_category- Tab name in the sidebar
3. Object Manipulation in Blender
Object manipulation is one of the most common tasks in Blender scripting. You can create, modify, transform, and delete objects programmatically.
Creating Objects
import bpy
# Add a cube
bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0))
cube = bpy.context.active_object
cube.name = "MyCube"
# Add a UV sphere
bpy.ops.mesh.primitive_uv_sphere_add(location=(3, 0, 0), radius=1.5)
sphere = bpy.context.active_object
Transforming Objects
import bpy
obj = bpy.data.objects["MyCube"]
# Move object
obj.location = (2, 3, 1)
# Rotate object (in radians)
obj.rotation_euler = (0, 0, 0.785) # 45 degrees
# Scale object
obj.scale = (1.5, 1.5, 1.5)
Selecting and Deleting Objects
import bpy
# Select all objects
bpy.ops.object.select_all(action='SELECT')
# Deselect all
bpy.ops.object.select_all(action='DESELECT')
# Select specific object
obj = bpy.data.objects.get("MyCube")
if obj:
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
# Delete selected object
bpy.ops.object.delete()
.get() before trying to manipulate it to avoid errors.
4. Materials and Textures Programming
Creating and assigning materials programmatically allows you to automate material setup for multiple objects.
Creating a Basic Material
import bpy
# Create a new material
mat = bpy.data.materials.new(name="MyMaterial")
mat.use_nodes = True
nodes = mat.node_tree.nodes
# Clear default nodes
nodes.clear()
# Add Principled BSDF
bsdf = nodes.new(type='ShaderNodeBsdfPrincipled')
bsdf.location = (0, 0)
# Add Material Output
output = nodes.new(type='ShaderNodeOutputMaterial')
output.location = (300, 0)
# Link nodes
links = mat.node_tree.links
links.new(bsdf.outputs['BSDF'], output.inputs['Surface'])
# Set color
bsdf.inputs['Base Color'].default_value = (0.8, 0.2, 0.1, 1.0) # RGBA
Assigning Material to Object
import bpy
obj = bpy.context.active_object
mat = bpy.data.materials.get("MyMaterial")
if obj and mat:
if obj.data.materials:
obj.data.materials[0] = mat
else:
obj.data.materials.append(mat)
5. Keyframe Animation and Scripting
Automate animation by inserting keyframes programmatically. This is useful for creating repetitive animations or procedural motion.
Basic Keyframing
import bpy
obj = bpy.context.active_object
# Set initial location at frame 1
bpy.context.scene.frame_set(1)
obj.location = (0, 0, 0)
obj.keyframe_insert(data_path="location", frame=1)
# Set final location at frame 50
bpy.context.scene.frame_set(50)
obj.location = (5, 5, 0)
obj.keyframe_insert(data_path="location", frame=50)
Animating Multiple Properties
import bpy
obj = bpy.context.active_object
scene = bpy.context.scene
for frame in range(1, 101, 10):
scene.frame_set(frame)
# Animate rotation
obj.rotation_euler.z = frame * 0.1
obj.keyframe_insert(data_path="rotation_euler", index=2, frame=frame)
# Animate scale
scale_factor = 1 + (frame / 100)
obj.scale = (scale_factor, scale_factor, scale_factor)
obj.keyframe_insert(data_path="scale", frame=frame)
6. Event Handling and User Input
Handle user interactions like mouse clicks and keyboard input to create interactive tools and operators.
Modal Operator Example
import bpy
class ModalOperator(bpy.types.Operator):
bl_idname = "object.modal_operator"
bl_label = "Modal Operator"
def modal(self, context, event):
if event.type == 'MOUSEMOVE':
# Update based on mouse movement
self.value = event.mouse_x
elif event.type == 'LEFTMOUSE':
# Confirm on left click
return {'FINISHED'}
elif event.type in {'RIGHTMOUSE', 'ESC'}:
# Cancel on right click or ESC
return {'CANCELLED'}
return {'RUNNING_MODAL'}
def invoke(self, context, event):
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def register():
bpy.utils.register_class(ModalOperator)
def unregister():
bpy.utils.unregister_class(ModalOperator)
7. Creating Custom Operators
Operators are actions that users can perform. Custom operators extend Blender's functionality with your own commands.
Simple Operator
import bpy
class SimpleOperator(bpy.types.Operator):
bl_idname = "object.simple_operator"
bl_label = "Simple Operator"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
# Create a cube at random location
import random
loc = (random.uniform(-5, 5), random.uniform(-5, 5), 0)
bpy.ops.mesh.primitive_cube_add(location=loc)
self.report({'INFO'}, "Cube added successfully")
return {'FINISHED'}
def register():
bpy.utils.register_class(SimpleOperator)
def unregister():
bpy.utils.unregister_class(SimpleOperator)
# Call the operator
# bpy.ops.object.simple_operator()
Operator with Properties
import bpy
from bpy.props import IntProperty, FloatProperty
class ParametricOperator(bpy.types.Operator):
bl_idname = "object.parametric_operator"
bl_label = "Parametric Operator"
bl_options = {'REGISTER', 'UNDO'}
count: IntProperty(name="Count", default=3, min=1, max=10)
radius: FloatProperty(name="Radius", default=2.0, min=0.1, max=10.0)
def execute(self, context):
import math
for i in range(self.count):
angle = (2 * math.pi / self.count) * i
x = self.radius * math.cos(angle)
y = self.radius * math.sin(angle)
bpy.ops.mesh.primitive_cube_add(location=(x, y, 0))
return {'FINISHED'}
8. Scene Data Access and Manipulation
Access and modify scene properties, including cameras, lights, world settings, and render properties.
Working with Scenes
import bpy
# Access current scene
scene = bpy.context.scene
# Scene properties
print(f"Scene name: {scene.name}")
print(f"Frame start: {scene.frame_start}")
print(f"Frame end: {scene.frame_end}")
# Modify scene settings
scene.frame_start = 1
scene.frame_end = 250
scene.render.fps = 30
# Create new scene
new_scene = bpy.data.scenes.new("MyNewScene")
bpy.context.window.scene = new_scene
Camera Setup
import bpy
# Create camera
bpy.ops.object.camera_add(location=(7, -7, 5))
camera = bpy.context.active_object
camera.rotation_euler = (1.1, 0, 0.785)
# Set as active camera
bpy.context.scene.camera = camera
# Camera properties
camera.data.lens = 35 # Focal length
camera.data.dof.use_dof = True # Enable depth of field
9. Custom UI Elements and Menus
Create custom menus, properties, and interactive UI elements for a professional add-on experience.
Custom Properties Panel
import bpy
from bpy.props import StringProperty, BoolProperty, FloatProperty
class MyProperties(bpy.types.PropertyGroup):
my_string: StringProperty(name="Name", default="Object")
my_bool: BoolProperty(name="Enable", default=True)
my_float: FloatProperty(name="Value", default=1.0, min=0.0, max=10.0)
class PropertiesPanel(bpy.types.Panel):
bl_label = "My Properties"
bl_idname = "PT_MyProperties"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Tool'
def draw(self, context):
layout = self.layout
scene = context.scene
mytool = scene.my_tool
layout.prop(mytool, "my_string")
layout.prop(mytool, "my_bool")
layout.prop(mytool, "my_float")
def register():
bpy.utils.register_class(MyProperties)
bpy.utils.register_class(PropertiesPanel)
bpy.types.Scene.my_tool = bpy.props.PointerProperty(type=MyProperties)
def unregister():
bpy.utils.unregister_class(PropertiesPanel)
bpy.utils.unregister_class(MyProperties)
del bpy.types.Scene.my_tool
10. Best Practices for Blender Scripting
Follow these best practices to write clean, efficient, and maintainable Blender scripts.
Code Organization
- Always include proper registration and unregistration functions
- Use meaningful variable and function names
- Group related functionality into classes
- Add docstrings to explain complex functions
Error Handling
import bpy
def safe_object_operation():
try:
obj = bpy.data.objects["MyCube"]
obj.location.z += 1
except KeyError:
print("Error: Object 'MyCube' not found")
return None
except Exception as e:
print(f"Unexpected error: {e}")
return None
return obj
Performance Tips
- Use
bpy.datainstead ofbpy.opswhen possible (faster) - Minimize scene updates during batch operations
- Use
depsgraph.update()sparingly - Cache frequently accessed data
11. Debugging Blender Scripts
Effective debugging techniques help you identify and fix issues in your scripts quickly.
Print Debugging
import bpy
# Print to system console
print("Debug: Object count =", len(bpy.data.objects))
# Print object properties
obj = bpy.context.active_object
if obj:
print(f"Object: {obj.name}")
print(f"Type: {obj.type}")
print(f"Location: {obj.location}")
print(f"Vertices: {len(obj.data.vertices) if obj.type == 'MESH' else 'N/A'}")
Using the Info Editor
The Info editor in Blender shows Python commands for actions you perform in the UI. This is invaluable for learning the API.
Error Reporting
import bpy
class SafeOperator(bpy.types.Operator):
bl_idname = "object.safe_operator"
bl_label = "Safe Operator"
def execute(self, context):
try:
# Your code here
obj = bpy.data.objects["NonExistent"]
except KeyError as e:
self.report({'ERROR'}, f"Object not found: {e}")
return {'CANCELLED'}
except Exception as e:
self.report({'ERROR'}, f"Unexpected error: {e}")
return {'CANCELLED'}
self.report({'INFO'}, "Operation completed successfully")
return {'FINISHED'}
12. Building Blender Add-ons
Add-ons are packaged Python scripts that can be installed and enabled in Blender's preferences.
Add-on Structure
bl_info = {
"name": "My Awesome Add-on",
"author": "Your Name",
"version": (1, 0),
"blender": (3, 0, 0),
"location": "View3D > Sidebar > Tool Tab",
"description": "Does awesome things",
"category": "Object",
}
import bpy
class MyOperator(bpy.types.Operator):
bl_idname = "object.my_operator"
bl_label = "My Operator"
def execute(self, context):
# Your code
return {'FINISHED'}
class MyPanel(bpy.types.Panel):
bl_label = "My Panel"
bl_idname = "PT_MyPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Tool'
def draw(self, context):
layout = self.layout
layout.operator("object.my_operator")
def register():
bpy.utils.register_class(MyOperator)
bpy.utils.register_class(MyPanel)
def unregister():
bpy.utils.unregister_class(MyPanel)
bpy.utils.unregister_class(MyOperator)
if __name__ == "__main__":
register()
13. Mesh Data Processing and Generation
Directly manipulate mesh data for procedural modeling, mesh analysis, and custom geometry creation.
Creating Mesh from Scratch
import bpy
import bmesh
# Create mesh data
mesh = bpy.data.meshes.new("CustomMesh")
obj = bpy.data.objects.new("CustomObject", mesh)
# Link to scene
bpy.context.collection.objects.link(obj)
# Create mesh using BMesh
bm = bmesh.new()
# Add vertices
v1 = bm.verts.new((0, 0, 0))
v2 = bm.verts.new((1, 0, 0))
v3 = bm.verts.new((1, 1, 0))
v4 = bm.verts.new((0, 1, 0))
# Create face
bm.faces.new([v1, v2, v3, v4])
# Update mesh
bm.to_mesh(mesh)
bm.free()
Modifying Existing Mesh
import bpy
import bmesh
obj = bpy.context.active_object
if obj and obj.type == 'MESH':
# Enter edit mode
bpy.ops.object.mode_set(mode='EDIT')
# Get BMesh
bm = bmesh.from_edit_mesh(obj.data)
# Move all vertices up
for vert in bm.verts:
vert.co.z += 0.5
# Update mesh
bmesh.update_edit_mesh(obj.data)
# Return to object mode
bpy.ops.object.mode_set(mode='OBJECT')
14. Integrating Blender with Other Libraries
Extend Blender's capabilities by integrating external Python libraries for data processing, AI, and more.
Using NumPy for Mesh Processing
import bpy
import numpy as np
obj = bpy.context.active_object
if obj and obj.type == 'MESH':
mesh = obj.data
# Get vertex coordinates as NumPy array
verts = np.zeros((len(mesh.vertices), 3))
mesh.vertices.foreach_get('co', verts.ravel())
# Apply transformation (e.g., scale all Z coordinates)
verts[:, 2] *= 1.5
# Update mesh
mesh.vertices.foreach_set('co', verts.ravel())
mesh.update()
Using JSON for Data Export
import bpy
import json
def export_scene_data(filepath):
scene_data = {
'objects': [],
'scene_name': bpy.context.scene.name
}
for obj in bpy.data.objects:
obj_data = {
'name': obj.name,
'type': obj.type,
'location': list(obj.location),
'rotation': list(obj.rotation_euler),
'scale': list(obj.scale)
}
scene_data['objects'].append(obj_data)
with open(filepath, 'w') as f:
json.dump(scene_data, f, indent=4)
print(f"Scene data exported to {filepath}")
# Usage
export_scene_data("/tmp/scene_data.json")
15. Lighting and Rendering Automation
Automate lighting setups and render settings for consistent results across multiple projects.
Creating Light Setup
import bpy
import math
def create_three_point_lighting(subject_location=(0, 0, 0)):
# Key light
bpy.ops.object.light_add(type='AREA', location=(4, -4, 5))
key_light = bpy.context.active_object
key_light.name = "KeyLight"
key_light.data.energy = 500
key_light.data.size = 2
# Point light at subject
direction = key_light.location - subject_location
key_light.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler()
# Fill light
bpy.ops.object.light_add(type='AREA', location=(-4, -2, 3))
fill_light = bpy.context.active_object
fill_light.name = "FillLight"
fill_light.data.energy = 200
# Back light
bpy.ops.object.light_add(type='SPOT', location=(0, 4, 4))
back_light = bpy.context.active_object
back_light.name = "BackLight"
back_light.data.energy = 300
create_three_point_lighting()
Render Settings Automation
import bpy
def setup_render_settings(resolution=(1920, 1080), samples=128, output_path="/tmp/render"):
scene = bpy.context.scene
# Resolution settings
scene.render.resolution_x = resolution[0]
scene.render.resolution_y = resolution[1]
scene.render.resolution_percentage = 100
# Render engine settings
scene.render.engine = 'CYCLES'
scene.cycles.samples = samples
scene.cycles.use_denoising = True
# Output settings
scene.render.filepath = output_path
scene.render.image_settings.file_format = 'PNG'
scene.render.image_settings.color_mode = 'RGBA'
print(f"Render settings configured: {resolution[0]}x{resolution[1]}, {samples} samples")
setup_render_settings()
# Batch render multiple frames
for frame in range(1, 11):
bpy.context.scene.frame_set(frame)
bpy.context.scene.render.filepath = f"/tmp/render_frame_{frame:04d}"
bpy.ops.render.render(write_still=True)
16. Curve and Surface Modeling
Create and manipulate curves and surfaces procedurally for organic modeling and path-based animations.
Creating Bezier Curves
import bpy
import math
# Create curve
curve_data = bpy.data.curves.new('MyCurve', type='CURVE')
curve_data.dimensions = '3D'
# Create spline
spline = curve_data.splines.new('BEZIER')
spline.bezier_points.add(3) # Add 3 more points (total 4)
# Set point positions
points = [
(0, 0, 0),
(2, 0, 1),
(4, 0, 0),
(6, 0, 1)
]
for i, point in enumerate(points):
bp = spline.bezier_points[i]
bp.co = point
bp.handle_left_type = 'AUTO'
bp.handle_right_type = 'AUTO'
# Create object
curve_obj = bpy.data.objects.new('CurveObject', curve_data)
bpy.context.collection.objects.link(curve_obj)
# Curve settings
curve_data.bevel_depth = 0.1
curve_data.resolution_u = 32
Spiral Generator
import bpy
import math
def create_spiral(turns=5, height=10, radius=2):
curve_data = bpy.data.curves.new('Spiral', type='CURVE')
curve_data.dimensions = '3D'
spline = curve_data.splines.new('NURBS')
points_per_turn = 20
num_points = turns * points_per_turn
spline.points.add(num_points - 1)
for i in range(num_points):
angle = (i / points_per_turn) * 2 * math.pi
z = (i / num_points) * height
x = radius * math.cos(angle)
y = radius * math.sin(angle)
spline.points[i].co = (x, y, z, 1)
curve_obj = bpy.data.objects.new('Spiral', curve_data)
bpy.context.collection.objects.link(curve_obj)
curve_data.bevel_depth = 0.05
return curve_obj
create_spiral()
# Path Animation
def animate_along_curve(obj, curve_obj, duration=100):
"""Animate object following a curve"""
# Add follow path constraint
constraint = obj.constraints.new('FOLLOW_PATH')
constraint.target = curve_obj
constraint.use_curve_follow = True
# Animate evaluation time
curve_obj.data.path_duration = duration
curve_obj.data.eval_time = 0
curve_obj.data.keyframe_insert(data_path="eval_time", frame=1)
curve_obj.data.eval_time = 100
curve_obj.data.keyframe_insert(data_path="eval_time", frame=duration)
17. Task Automation with bpy
Automate repetitive tasks to save time and ensure consistency across your workflow.
Batch Object Operations
import bpy
def batch_rename_objects(prefix="Object", start_number=1):
"""Rename all selected objects with sequential numbering"""
selected = bpy.context.selected_objects
for i, obj in enumerate(selected, start=start_number):
obj.name = f"{prefix}_{i:03d}"
print(f"Renamed {len(selected)} objects")
def apply_material_to_selection(material_name):
"""Apply material to all selected objects"""
mat = bpy.data.materials.get(material_name)
if not mat:
print(f"Material '{material_name}' not found")
return
for obj in bpy.context.selected_objects:
if obj.type == 'MESH':
if obj.data.materials:
obj.data.materials[0] = mat
else:
obj.data.materials.append(mat)
# Usage
batch_rename_objects("Cube", 1)
apply_material_to_selection("MyMaterial")
Automated Scene Setup
import bpy
def setup_product_shot():
"""Create a complete product shot scene"""
# Clear existing objects
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
# Add product platform
bpy.ops.mesh.primitive_cylinder_add(radius=3, depth=0.2, location=(0, 0, 0))
platform = bpy.context.active_object
platform.name = "Platform"
# Add camera
bpy.ops.object.camera_add(location=(6, -6, 4))
camera = bpy.context.active_object
camera.rotation_euler = (1.1, 0, 0.785)
bpy.context.scene.camera = camera
# Add lights
bpy.ops.object.light_add(type='AREA', location=(5, -5, 5))
light1 = bpy.context.active_object
light1.data.energy = 500
bpy.ops.object.light_add(type='AREA', location=(-5, -2, 3))
light2 = bpy.context.active_object
light2.data.energy = 200
# Set render settings
scene = bpy.context.scene
scene.render.resolution_x = 1920
scene.render.resolution_y = 1080
scene.render.engine = 'CYCLES'
scene.cycles.samples = 128
print("Product shot scene created successfully")
setup_product_shot()
18. Custom Tool Development
Develop custom tools that integrate seamlessly into Blender's interface for specialized workflows.
Array Tool Example
import bpy
from bpy.props import IntProperty, FloatProperty, EnumProperty
class ArrayToolOperator(bpy.types.Operator):
bl_idname = "object.array_tool"
bl_label = "Array Tool"
bl_options = {'REGISTER', 'UNDO'}
count: IntProperty(
name="Count",
description="Number of duplicates",
default=5,
min=1,
max=100
)
spacing: FloatProperty(
name="Spacing",
description="Distance between objects",
default=2.0,
min=0.1,
max=100.0
)
axis: EnumProperty(
name="Axis",
items=[
('X', "X Axis", ""),
('Y', "Y Axis", ""),
('Z', "Z Axis", "")
],
default='X'
)
def execute(self, context):
obj = context.active_object
if not obj:
self.report({'WARNING'}, "No active object")
return {'CANCELLED'}
for i in range(1, self.count):
# Duplicate object
new_obj = obj.copy()
new_obj.data = obj.data.copy()
context.collection.objects.link(new_obj)
# Position based on axis
offset = i * self.spacing
if self.axis == 'X':
new_obj.location.x = obj.location.x + offset
elif self.axis == 'Y':
new_obj.location.y = obj.location.y + offset
else:
new_obj.location.z = obj.location.z + offset
self.report({'INFO'}, f"Created {self.count - 1} duplicates")
return {'FINISHED'}
class ArrayToolPanel(bpy.types.Panel):
bl_label = "Array Tool"
bl_idname = "PT_ArrayTool"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = 'Tool'
def draw(self, context):
layout = self.layout
layout.operator("object.array_tool")
def register():
bpy.utils.register_class(ArrayToolOperator)
bpy.utils.register_class(ArrayToolPanel)
def unregister():
bpy.utils.unregister_class(ArrayToolPanel)
bpy.utils.unregister_class(ArrayToolOperator)
19. Collection Management in Blender
Organize your scenes effectively by managing collections programmatically.
Creating and Managing Collections
import bpy
def create_collection(name, parent=None):
"""Create a new collection"""
collection = bpy.data.collections.new(name)
if parent:
parent.children.link(collection)
else:
bpy.context.scene.collection.children.link(collection)
return collection
def move_to_collection(obj, collection_name):
"""Move object to specified collection"""
collection = bpy.data.collections.get(collection_name)
if not collection:
collection = create_collection(collection_name)
# Remove from all collections
for coll in obj.users_collection:
coll.objects.unlink(obj)
# Add to new collection
collection.objects.link(obj)
# Example usage
lights_col = create_collection("Lights")
geometry_col = create_collection("Geometry")
# Organize objects by type
for obj in bpy.data.objects:
if obj.type == 'LIGHT':
move_to_collection(obj, "Lights")
elif obj.type == 'MESH':
move_to_collection(obj, "Geometry")
elif obj.type == 'CAMERA':
move_to_collection(obj, "Cameras")
Collection Hierarchy Setup
import bpy
def setup_production_collections():
"""Create standard production collection hierarchy"""
# Main collections
assets = create_collection("Assets")
lighting = create_collection("Lighting")
cameras = create_collection("Cameras")
# Asset subcollections
characters = create_collection("Characters", assets)
props = create_collection("Props", assets)
environments = create_collection("Environments", assets)
# Hide collections from viewport
lighting.hide_viewport = False
assets.hide_render = False
print("Production collections created")
setup_production_collections()
20. Optimizing Blender Scripts
Write efficient scripts that execute quickly even with large datasets.
Performance Comparison
import bpy
import time
# SLOW: Using operators
start = time.time()
for i in range(100):
bpy.ops.mesh.primitive_cube_add(location=(i, 0, 0))
slow_time = time.time() - start
# Delete objects
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
# FAST: Using bpy.data
start = time.time()
mesh = bpy.data.meshes.new("Cube")
for i in range(100):
obj = bpy.data.objects.new(f"Cube_{i}", mesh)
obj.location = (i, 0, 0)
bpy.context.collection.objects.link(obj)
fast_time = time.time() - start
print(f"Slow method: {slow_time:.3f}s")
print(f"Fast method: {fast_time:.3f}s")
print(f"Speedup: {slow_time/fast_time:.1f}x")
Batch Operations
import bpy
def optimize_mesh_batch():
"""Optimize multiple meshes efficiently"""
# Store current mode
mode = bpy.context.mode
# Get all mesh objects
mesh_objects = [obj for obj in bpy.data.objects if obj.type == 'MESH']
# Batch process
for obj in mesh_objects:
# Use data-level operations instead of operators
mesh = obj.data
# Remove doubles by direct mesh manipulation
# This is faster than using operators for each object
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles(threshold=0.0001)
bpy.ops.object.mode_set(mode='OBJECT')
print(f"Optimized {len(mesh_objects)} meshes")
optimize_mesh_batch()
- Use
bpy.datainstead ofbpy.opswhen possible - Cache frequently accessed data in variables
- Use list comprehensions for filtering
- Minimize scene updates during batch operations
- Profile your code to find bottlenecks
21. Geometry Nodes with Python
Control and automate Geometry Nodes through Python for procedural modeling workflows.
Creating Geometry Nodes Modifier
import bpy
def add_geometry_nodes_modifier(obj, node_group_name="Geometry Nodes"):
"""Add geometry nodes modifier to object"""
# Add modifier
modifier = obj.modifiers.new(name="GeometryNodes", type='NODES')
# Create node group if it doesn't exist
if node_group_name not in bpy.data.node_groups:
node_group = bpy.data.node_groups.new(node_group_name, 'GeometryNodeTree')
# Add input and output nodes
input_node = node_group.nodes.new('NodeGroupInput')
output_node = node_group.nodes.new('NodeGroupOutput')
input_node.location = (-200, 0)
output_node.location = (200, 0)
# Create sockets
node_group.interface.new_socket('Geometry', in_out='INPUT', socket_type='NodeSocketGeometry')
node_group.interface.new_socket('Geometry', in_out='OUTPUT', socket_type='NodeSocketGeometry')
else:
node_group = bpy.data.node_groups[node_group_name]
modifier.node_group = node_group
return modifier
# Example: Add to active object
obj = bpy.context.active_object
if obj:
add_geometry_nodes_modifier(obj)
Programmatic Node Setup
import bpy
def create_subdivision_node_group():
"""Create a geometry nodes setup with subdivision"""
# Create node group
node_group = bpy.data.node_groups.new("Subdivision Setup", 'GeometryNodeTree')
# Create interface
node_group.interface.new_socket('Geometry', in_out='INPUT', socket_type='NodeSocketGeometry')
node_group.interface.new_socket('Geometry', in_out='OUTPUT', socket_type='NodeSocketGeometry')
node_group.interface.new_socket('Level', in_out='INPUT', socket_type='NodeSocketInt')
# Add nodes
input_node = node_group.nodes.new('NodeGroupInput')
output_node = node_group.nodes.new('NodeGroupOutput')
subdiv_node = node_group.nodes.new('GeometryNodeSubdivisionSurface')
# Position nodes
input_node.location = (-400, 0)
subdiv_node.location = (-100, 0)
output_node.location = (200, 0)
# Connect nodes
links = node_group.links
links.new(input_node.outputs['Geometry'], subdiv_node.inputs['Mesh'])
links.new(input_node.outputs['Level'], subdiv_node.inputs['Level'])
links.new(subdiv_node.outputs['Mesh'], output_node.inputs['Geometry'])
return node_group
# Apply to active object
obj = bpy.context.active_object
if obj and obj.type == 'MESH':
modifier = obj.modifiers.new(name="GeoNodes", type='NODES')
modifier.node_group = create_subdivision_node_group()
modifier["Input_2"] = 2 # Set subdivision level
22. Asset Browser Automation
Manage and organize assets programmatically for efficient asset library workflows.
Marking Assets
import bpy
def mark_as_asset(obj, catalog_path=""):
"""Mark object as asset"""
obj.asset_mark()
obj.asset_data.catalog_simple_name = catalog_path
# Add metadata
obj.asset_data.description = f"Asset: {obj.name}"
obj.asset_data.author = "Your Name"
print(f"Marked '{obj.name}' as asset")
def batch_mark_assets(object_names, catalog="Props"):
"""Mark multiple objects as assets"""
for name in object_names:
obj = bpy.data.objects.get(name)
if obj:
mark_as_asset(obj, catalog)
# Example usage
selected_objects = [obj.name for obj in bpy.context.selected_objects]
batch_mark_assets(selected_objects, "Characters/NPCs")
Asset Library Management
import bpy
import os
def export_asset_library(output_dir):
"""Export all marked assets to blend files"""
if not os.path.exists(output_dir):
os.makedirs(output_dir)
asset_objects = [obj for obj in bpy.data.objects if obj.asset_data]
for obj in asset_objects:
# Create new blend file for each asset
filepath = os.path.join(output_dir, f"{obj.name}.blend")
# Select only this object
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
# Save as separate file
bpy.data.libraries.write(filepath, {obj})
print(f"Exported: {filepath}")
# Usage
# export_asset_library("/path/to/asset/library")
23. Rigging and Armature Scripting
Create and manipulate armatures and rigs programmatically for character animation.
Creating Simple Armature
import bpy
def create_simple_rig(bone_chain_length=5, bone_length=1.0):
"""Create a simple bone chain"""
# Create armature
bpy.ops.object.armature_add(location=(0, 0, 0))
armature_obj = bpy.context.active_object
armature_obj.name = "SimpleRig"
# Enter edit mode
bpy.ops.object.mode_set(mode='EDIT')
armature = armature_obj.data
# Remove default bone
armature.edit_bones.remove(armature.edit_bones[0])
# Create bone chain
previous_bone = None
for i in range(bone_chain_length):
bone = armature.edit_bones.new(f"Bone_{i:02d}")
bone.head = (0, 0, i * bone_length)
bone.tail = (0, 0, (i + 1) * bone_length)
# Parent to previous bone
if previous_bone:
bone.parent = previous_bone
previous_bone = bone
# Return to object mode
bpy.ops.object.mode_set(mode='OBJECT')
return armature_obj
# Create rig
rig = create_simple_rig()
Bone Constraints
import bpy
def add_ik_chain(armature_obj, chain_start="Bone_00", chain_end="Bone_04"):
"""Add IK constraint to bone chain"""
# Set to pose mode
bpy.context.view_layer.objects.active = armature_obj
bpy.ops.object.mode_set(mode='POSE')
# Get bones
pose_bones = armature_obj.pose.bones
end_bone = pose_bones.get(chain_end)
if end_bone:
# Add IK constraint
ik_constraint = end_bone.constraints.new('IK')
ik_constraint.target = armature_obj
ik_constraint.subtarget = chain_start
ik_constraint.chain_count = 5
print(f"IK constraint added to {chain_end}")
bpy.ops.object.mode_set(mode='OBJECT')
# Usage
armature = bpy.data.objects.get("SimpleRig")
if armature:
add_ik_chain(armature)
Weight Painting Automation
import bpy
def auto_weight_paint(mesh_obj, armature_obj):
"""Automatically assign vertex groups for armature"""
# Select mesh and armature
bpy.ops.object.select_all(action='DESELECT')
mesh_obj.select_set(True)
armature_obj.select_set(True)
bpy.context.view_layer.objects.active = armature_obj
# Parent with automatic weights
bpy.ops.object.parent_set(type='ARMATURE_AUTO')
print(f"Auto weights applied to {mesh_obj.name}")
# Usage
# mesh = bpy.data.objects.get("Character")
# rig = bpy.data.objects.get("SimpleRig")
# if mesh and rig:
# auto_weight_paint(mesh, rig)
24. Physics & Simulation Control
Control physics simulations including rigid body, cloth, and fluid dynamics through Python.
Rigid Body Physics
import bpy
def setup_rigid_body_scene():
"""Create a scene with rigid body physics"""
# Create ground plane
bpy.ops.mesh.primitive_plane_add(size=20, location=(0, 0, 0))
ground = bpy.context.active_object
ground.name = "Ground"
# Add rigid body (passive)
bpy.ops.rigidbody.object_add()
ground.rigid_body.type = 'PASSIVE'
ground.rigid_body.collision_shape = 'MESH'
# Create falling cubes
for i in range(5):
for j in range(5):
bpy.ops.mesh.primitive_cube_add(
location=(i * 2 - 4, j * 2 - 4, 10 + i * 2)
)
cube = bpy.context.active_object
cube.name = f"Cube_{i}_{j}"
# Add rigid body (active)
bpy.ops.rigidbody.object_add()
cube.rigid_body.type = 'ACTIVE'
cube.rigid_body.mass = 1.0
cube.rigid_body.friction = 0.5
cube.rigid_body.restitution = 0.3
# Set simulation range
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 250
print("Rigid body scene created")
setup_rigid_body_scene()
Cloth Simulation
import bpy
def add_cloth_simulation(obj):
"""Add cloth physics to object"""
# Add cloth modifier
cloth_mod = obj.modifiers.new(name="Cloth", type='CLOTH')
# Cloth settings
cloth_mod.settings.quality = 5
cloth_mod.settings.mass = 0.3
cloth_mod.settings.tension_stiffness = 15
cloth_mod.settings.compression_stiffness = 15
cloth_mod.settings.shear_stiffness = 5
cloth_mod.settings.bending_stiffness = 0.5
# Collision settings
cloth_mod.collision_settings.use_collision = True
cloth_mod.collision_settings.distance_min = 0.015
print(f"Cloth simulation added to {obj.name}")
return cloth_mod
# Example: Add cloth to a plane
bpy.ops.mesh.primitive_plane_add(size=4, location=(0, 0, 5))
cloth_obj = bpy.context.active_object
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.subdivide(number_cuts=20)
bpy.ops.object.mode_set(mode='OBJECT')
add_cloth_simulation(cloth_obj)
Particle Systems
import bpy
def create_particle_emitter(obj, particle_count=1000):
"""Add particle system to object"""
# Add particle system
particle_mod = obj.modifiers.new(name="Particles", type='PARTICLE_SYSTEM')
psys = obj.particle_systems[-1]
settings = psys.settings
# Particle settings
settings.count = particle_count
settings.lifetime = 100
settings.frame_start = 1
settings.frame_end = 50
# Physics settings
settings.physics_type = 'NEWTON'
settings.mass = 1.0
settings.use_die_on_collision = False
settings.normal_factor = 1.0
# Render settings
settings.render_type = 'OBJECT'
print(f"Particle system added to {obj.name}")
return psys
# Example usage
bpy.ops.mesh.primitive_ico_sphere_add(location=(0, 0, 5))
emitter = bpy.context.active_object
create_particle_emitter(emitter, 500)
25. Pipeline & Production Integration
Integrate Blender into larger production pipelines with file I/O, batch processing, and workflow automation.
Batch File Processing
import bpy
import os
import glob
def batch_process_files(input_dir, output_dir, process_func):
"""Process multiple blend files"""
blend_files = glob.glob(os.path.join(input_dir, "*.blend"))
for filepath in blend_files:
print(f"Processing: {filepath}")
# Load file
bpy.ops.wm.open_mainfile(filepath=filepath)
# Apply processing function
process_func()
# Save to output directory
filename = os.path.basename(filepath)
output_path = os.path.join(output_dir, filename)
bpy.ops.wm.save_as_mainfile(filepath=output_path)
print(f"Saved: {output_path}")
def optimize_scene():
"""Example processing function"""
# Remove unused data
bpy.ops.outliner.orphans_purge(do_recursive=True)
# Optimize all meshes
for obj in bpy.data.objects:
if obj.type == 'MESH':
bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles()
bpy.ops.object.mode_set(mode='OBJECT')
# Usage
# batch_process_files("/input/folder", "/output/folder", optimize_scene)
Command Line Rendering
import bpy
import sys
import os
def setup_commandline_render():
"""Configure scene for command line rendering"""
# Get command line arguments
argv = sys.argv
argv = argv[argv.index("--") + 1:] if "--" in argv else []
# Parse arguments
output_path = argv[0] if len(argv) > 0 else "/tmp/render"
frame_start = int(argv[1]) if len(argv) > 1 else 1
frame_end = int(argv[2]) if len(argv) > 2 else 250
# Configure scene
scene = bpy.context.scene
scene.render.filepath = output_path
scene.frame_start = frame_start
scene.frame_end = frame_end
scene.render.image_settings.file_format = 'PNG'
# Render
bpy.ops.render.render(animation=True)
print(f"Rendered frames {frame_start}-{frame_end} to {output_path}")
# Run from command line:
# blender --background scene.blend --python script.py -- /output/path 1 100
FBX/USD Export Pipeline
import bpy
import os
def export_pipeline(export_dir, formats=['FBX', 'USD']):
"""Export all objects in multiple formats"""
if not os.path.exists(export_dir):
os.makedirs(export_dir)
for obj in bpy.data.objects:
if obj.type == 'MESH':
# Select only this object
bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True)
bpy.context.view_layer.objects.active = obj
# Export in each format
for fmt in formats:
filepath = os.path.join(export_dir, f"{obj.name}.{fmt.lower()}")
if fmt == 'FBX':
bpy.ops.export_scene.fbx(
filepath=filepath,
use_selection=True,
apply_scale_options='FBX_SCALE_ALL'
)
elif fmt == 'USD':
bpy.ops.wm.usd_export(
filepath=filepath,
selected_objects_only=True
)
elif fmt == 'OBJ':
bpy.ops.wm.obj_export(
filepath=filepath,
export_selected_objects=True
)
print(f"Exported: {filepath}")
# Usage
# export_pipeline("/path/to/export", ['FBX', 'USD', 'OBJ'])
Version Control Integration
import bpy
import json
import datetime
def save_scene_metadata(filepath):
"""Save scene metadata for version control"""
metadata = {
'filename': bpy.data.filepath,
'timestamp': datetime.datetime.now().isoformat(),
'blender_version': bpy.app.version_string,
'object_count': len(bpy.data.objects),
'material_count': len(bpy.data.materials),
'frame_range': [bpy.context.scene.frame_start, bpy.context.scene.frame_end],
'render_engine': bpy.context.scene.render.engine,
'objects': []
}
# Collect object information
for obj in bpy.data.objects:
obj_info = {
'name': obj.name,
'type': obj.type,
'vertex_count': len(obj.data.vertices) if obj.type == 'MESH' else 0,
'materials': [mat.name for mat in obj.data.materials] if hasattr(obj.data, 'materials') else []
}
metadata['objects'].append(obj_info)
# Save to JSON
with open(filepath, 'w') as f:
json.dump(metadata, f, indent=4)
print(f"Metadata saved to {filepath}")
# Usage
# save_scene_metadata("/path/to/scene_metadata.json")
- Always validate file paths before operations
- Implement error logging for batch processes
- Use environment variables for cross-platform compatibility
- Create backup systems for critical operations
- Document your pipeline scripts thoroughly
🎓 Conclusion & Next Steps
Congratulations! You've now learned the fundamentals and advanced concepts of the Blender Python API. Here's how to continue your learning journey:
Practice Projects
- Procedural City Generator: Combine object manipulation, collections, and materials to create a random city generator
- Animation Toolkit: Build a custom add-on with operators for common animation tasks
- Asset Manager: Create a tool for organizing and batch-processing assets
- Render Farm Controller: Develop a script to distribute renders across multiple machines
Resources
- Official Documentation: docs.blender.org/api/current/
- Blender Stack Exchange: blender.stackexchange.com
- Developer Forum: devtalk.blender.org
- Source Code: Study Blender's built-in add-ons for examples
Advanced Topics to Explore
- Custom Render Engines
- Viewport Drawing (BGL/GPU modules)
- Custom Node Types
- C/C++ Python Extensions
- Machine Learning Integration